package applets;

import java.io.BufferedOutputStream;
import java.io.DataOutputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Vector;

import marchingCubes.IsMCVertex;
import marchingCubes.MCube;
import marchingCubes.MCubeEngine;
import marchingCubes.MCubeEngineLowMemory;
import meshToSolid.ContainerGrid;
import meshToSolid.STLWriter;
import meshToSolid.ContainerGrid.Container;

import peasy.PeasyCam;
import pobj.SimpleOBJExporterFast;
import processing.core.PApplet;
import ptools.PGraphicsHelper;
import simpleGeom.Bounds3D;
import simpleGeom.Box3D;
import simpleGeom.Face3D;
import simpleGeom.HasBounds3D;
import simpleGeom.HasPos3D;

import simpleGeom.Node;
import simpleGeom.SimpleObjLoaderStream;
import simpleGeom.Triangle3D;
import simpleGeom.TrianglePreCalc;
import simpleGeom.Vec3D;

public class MeshToSolid extends PApplet {
	ArrayList<Node> nodes = new ArrayList<Node>();
	ArrayList<Face3D> faces = new ArrayList<Face3D>();
	Bounds3D b3d = new Bounds3D();
	Cell[][][] grid;
	ContainerGrid cGrid;
	int cGridRes = 20;
	//test

	float sX;
	float sY;
	float sZ;
	int nX;
	int nY;
	int nZ;
	float dim = 0.02f;// 0.003
	float iso = 0.041f;// 0.005//0.006
	String folderPath = "/Users/administrator/Desktop/2012-EXTERN-Grotto/grotto120830/";
	String pathObjIn = folderPath + "testCyl.obj";
	String pathObjOut = folderPath + "myTest";
	String pathDistOut = folderPath + "testDistHigh4.txt";
	Vector<Face3D> meFaces;

	public void saveSTL(String file) {
		STLWriter writer = new STLWriter();
		writer.beginSave(file + ".stl", meFaces.size());
		for (int i = 0; i < meFaces.size(); i++) {
			Face3D f = meFaces.get(i);
			writer.face(f.getNode(0), f.getNode(1), f.getNode(2));
		}
		writer.endSave();
	}

	public void setup() {
		size(1600, 900, P3D);

		SimpleObjLoaderStream stream = new SimpleObjLoaderStream();
		stream.init(pathObjIn);
		float[] vertex = stream.readVertex();
		while (vertex != null) {
			nodes.add(new Node(vertex[0], vertex[1], vertex[2]));
			vertex = stream.readVertex();
		}
		println("nodes: " + nodes.size());
		stream.close();
		stream.init(pathObjIn);
		int[] face = stream.readFace();
		while (face != null) {
			Face3D f = new Face3D();
			if (face.length == 4) {
				f = new TrianglePreCalc(nodes.get(face[0] - 1),
						nodes.get(face[1] - 1), nodes.get(face[2] - 1));
				faces.add(f);
				f = new TrianglePreCalc(nodes.get(face[2] - 1),
						nodes.get(face[3] - 1), nodes.get(face[0] - 1));
				faces.add(f);

				for (int i = 0; i < face.length; i++) {
					Node n = nodes.get(face[i] - 1);
					n.setComment(1);
				}
			} else {
				f = new TrianglePreCalc(nodes.get(face[0] - 1),
						nodes.get(face[1] - 1), nodes.get(face[2] - 1));
				for (int i = 0; i < face.length; i++) {
					Node n = nodes.get(face[i] - 1);
					n.setComment(1);
				}
				faces.add(f);
			}
			face = stream.readFace();
		}
		ArrayList<Node> newNodes = new ArrayList<Node>();
		for (int i = 0; i < nodes.size(); i++) {
			Node n = nodes.get(i);
			if (n.getComment() == 1) {
				newNodes.add(n);
			}
		}
		nodes = newNodes;
		b3d.addAll(nodes);

		stream.close();
		PeasyCam cam = new PeasyCam(this, b3d.getMaxDimension() * 500);
		// b3d.setY2(b3d.getDimY()*0.5f+b3d.getY1());
		Box3D box3D = b3d.getOffsetBox(-iso * 1f);
		b3d.x1 = box3D.x1;
		b3d.y1 = box3D.y1;
		b3d.z1 = box3D.z1;
		b3d.x2 = box3D.x2;
		b3d.y2 = box3D.y2;
		b3d.z2 = box3D.z2;
		// b3d.z2=box3D.z1+box3D.getDimZ()*0.5f;
		// b3d.y2=box3D.y1+box3D.getDimY()*0.15f;

		nX = (int) (b3d.getDimX() / dim);
		nY = (int) (b3d.getDimY() / dim);
		nZ = (int) (b3d.getDimZ() / dim);

		sX = b3d.getX1();
		sY = b3d.getY1();
		sZ = b3d.getZ1();
		grid = new Cell[nX][nY][nZ];
		println(nX + " " + nY + " " + nZ);

		for (int x = 0; x < nX; x++) {
			for (int y = 0; y < nY; y++) {
				for (int z = 0; z < nZ; z++) {
					Cell cell = new Cell(x * dim + sX, y * dim + sY, z * dim
							+ sZ);
					grid[x][y][z] = cell;
				}
			}
		}

		cGrid = new ContainerGrid(b3d, dim);
		for (int i = 0; i < faces.size(); i++) {
			Face3D f = faces.get(i);
			cGrid.addObject(f, iso);
		}

		println("opened");

		calculateDistances();
		println("startOutline");
		findOutline(nX - 1, (int) (nY * 0.5f), nZ - 1);
		println("endOutline");
		meFaces = MCubeEngineLowMemory.initCubesTest(grid, iso);

		/* saveDistance(pathDistOut); */
		this.saveObjPrintwriter(pathObjOut);

		// saveSTL(pathObjOut);

	}

	public void loadDistance(String file) {
		String[] lines = this.loadStrings(file);
		String cString = lines[0];
		String[] strings = cString.split(" ");
		int nX = Integer.valueOf(strings[0]);
		println("nX: " + nX);
		int nY = Integer.valueOf(strings[1]);
		int nZ = Integer.valueOf(strings[2]);

		grid = new Cell[nX][nY][nZ];
		println(nY);
		int j = 0;
		for (int i = 1; i < lines.length; i++) {
			String l = lines[i];
			j = i - 1;
			int cX = ((int) (j * 1f / (nZ * nY)));
			int cY = (int) (j * 1f / nZ) % nY;
			int cZ = j % (nZ);
			double d = Double.valueOf(l);
			grid[cX][cY][cZ].d = (float) d;
		}
	}

	public void saveDistance(String file) {
		try {
			FileOutputStream fileOutputStream = new FileOutputStream(file);
			PrintStream printstream = new PrintStream(fileOutputStream);
			for (int x = 0; x < grid.length; x++) {
				for (int y = 0; y < grid[x].length; y++) {
					for (int z = 0; z < grid[x][y].length; z++) {
						Cell c = grid[x][y][z];
						printstream.println("" + c.d);
					}
				}
			}
			printstream.close();
			fileOutputStream.close();
		} catch (Exception e) {
			System.err.println("error writing file");
		}
	}

	public void saveObj(String file) {
		int idV = 1;
		SimpleOBJExporterFast sO = new SimpleOBJExporterFast(file);
		for (int i = 0; i < meFaces.size(); i++) {
			Face3D f = meFaces.get(i);
			for (int j = 0; j < f.getNNodes(); j++) {
				Vec3D n = (Vec3D) f.getNode(j);
				if (n.getId() == 0) {
					n.setId(idV);
					idV++;
					sO.writeVertex(n.x, n.y, n.z);
				}
			}
		}
		for (int i = 0; i < meFaces.size(); i++) {
			Face3D f = meFaces.get(i);
			int[] ids = new int[f.getNNodes()];
			for (int j = 0; j < f.getNNodes(); j++) {
				Vec3D n = (Vec3D) f.getNode(j);
				ids[j] = n.getId();
			}
			sO.writeFace(ids);
		}
		sO.close();
	}

	public void saveObjPrintwriter(String file) {
		PrintWriter output = createWriter(file + ".obj");

		int idV = 1;
		for (int i = 0; i < meFaces.size(); i++) {
			Face3D f = meFaces.get(i);
			for (int j = 0; j < f.getNNodes(); j++) {
				Vec3D n = (Vec3D) f.getNode(j);
				if (n.getId() == 0) {
					n.setId(idV);
					idV++;
					output.println("v " + n.x + " " + n.y + " " + n.z);
				}
			}
		}
		for (int i = 0; i < meFaces.size(); i++) {
			Face3D f = meFaces.get(i);
			int[] ids = new int[f.getNNodes()];
			String string = "f";
			for (int j = 0; j < f.getNNodes(); j++) {
				Vec3D n = (Vec3D) f.getNode(j);
				string += " " + n.getId();
			}
			output.println(string);
		}
		output.flush(); // Writes the remaining data to the file
		output.close();
	}

	public void findOutline(int cX, int cY, int cZ) {
		Vector<Cell> candidates = new Vector<Cell>();
		/*
		 * for (int x=0;x<nX;x++){ for (int y=0;y<nY;y++){
		 * candidates.add(grid[x][y][0]); candidates.add(grid[x][y][nZ-1]); } }
		 * for (int x=0;x<nX;x++){ for (int z=0;z<nZ;z++){
		 * candidates.add(grid[x][0][z]); candidates.add(grid[x][nY-1][z]); } }
		 * for (int y=0;y<nY;y++){ for (int z=0;z<nZ;z++){
		 * candidates.add(grid[0][y][z]); candidates.add(grid[nX-1][y][z]); } }
		 */
		candidates.add(grid[cX][cY][cZ]);
		for (int i = 0; i < candidates.size(); i++) {
			Cell cell = candidates.get(i);
			cell.inside = true;
		}

		while (candidates.size() > 0) {
			Vector<Cell> nextCandidates = new Vector<Cell>();
			for (int i = 0; i < candidates.size(); i++) {
				Cell cell = candidates.get(i);
				int sX = max(xK(cell.x) - 1, 0);
				int sY = max(yK(cell.y) - 1, 0);
				int sZ = max(zK(cell.z) - 1, 0);

				int eX = min(xK(cell.x) + 2, nX);
				int eY = min(yK(cell.y) + 2, nY);
				int eZ = min(zK(cell.z) + 2, nZ);
				println(sX + "  " + eX + "  " + xK(cell.x));
				for (int x = sX; x < eX; x++) {
					for (int y = sY; y < eY; y++) {
						for (int z = sZ; z < eZ; z++) {
							Cell nb = grid[x][y][z];
							if (!nb.inside&&nb.getValue()>cell.getValue()) {
								nb.inside = true;
								nextCandidates.add(nb);
							}
						}
					}
				}
			}
			candidates = nextCandidates;
		}
		for (int x = 0; x < nX; x++) {
			for (int y = 0; y < nY; y++) {
				for (int z = 0; z < nZ; z++) {
					Cell cell = grid[x][y][z];
					if (!cell.inside && cell.getValue() > iso) {
						cell.setValue(0);
					}
				}
			}
		}
	}

	public void calculateDistances() {
		for (int x = 0; x < grid.length; x++) {
			println("x: " + x);
			for (int y = 0; y < grid[x].length; y++) {

				float maxD = iso;
				for (int z = 0; z < grid[x][y].length; z++) {
					Cell cell = grid[x][y][z];

					Box3D b = new Box3D();
					b.x1 = cell.x - maxD;
					b.y1 = cell.y - maxD;
					b.z1 = cell.z - maxD;
					b.x2 = cell.x + maxD;
					b.y2 = cell.y + maxD;
					b.z2 = cell.z + maxD;
					Vector<HasBounds3D> faces = cGrid.getObjects(b);

					for (int i = 0; i < faces.size(); i++) {
						Triangle3D f = (Triangle3D) faces.get(i);
						float cD = f.getDistance(cell.x, cell.y, cell.z);
						if (cD < cell.d)
							cell.d = cD;
					}

				}
			}
		}
	}

	public int xK(float x) {
		return Math.round((x - sX) / dim);
	}

	public int yK(float y) {
		return Math.round((y - sY) / dim);
	}

	public int zK(float z) {
		return Math.round((z - sZ) / dim);
	}

	public void draw() {
		rectMode(CENTER);
		background(255);
		noStroke();
		fill(255);
		scale(400);
		translate(-b3d.getCenterX(), -b3d.getCenterY(), -b3d.getCenterZ());
		pointLight(200, 200, 200, b3d.getDimX(), b3d.getDimY(), b3d.getDimZ());
		pointLight(200, 200, 200, -b3d.getDimX(), -b3d.getDimY(),
				-b3d.getDimZ());
		PGraphicsHelper.drawWorldAxis3D(g, 0.5f);
		for (int i = 0; i < faces.size(); i++) {
			Face3D f = faces.get(i);
			// f.display3D(g);
			Box3D b3d = f.getBounds3D();
			noFill();
			stroke(0, 255, 0);
			// b3d.display3D(g);
		}

		stroke(255, 0, 0);
		noFill();
		for (int x = 0; x < cGrid.grid.length; x++) {
			for (int y = 0; y < cGrid.grid[x].length; y++) {
				for (int z = 0; z < cGrid.grid[x][y].length; z++) {
					Container c = cGrid.get(x, y, z);
					if (c.objects.size() > 0) {
						// c.display3D(g);
					}
				}
			}
		}

		stroke(255, 0, 0);
		/*
		 * for (int x=0;x<grid.length;x++){ for (int y=0;y<grid[x].length;y++){
		 * for (int z=0;z<grid[x][y].length;z++){ Cell c=grid[x][y][z]; if
		 * (c.getValue()<iso){ fill(255); } else{ noFill(); } pushMatrix();
		 * translate(c.x,c.y,c.z); box(dim); popMatrix();
		 * 
		 * 
		 * 
		 * } } }
		 */

		/*
		 * scale(dim); translate(-nX*0.5f,-nY*0.5f,-nZ*0.5f);
		 */

		for (int i = 0; i < meFaces.size(); i++) {
			Face3D f = meFaces.get(i);
			f.display3D(g);
		}
	}

	public class Cell extends Vec3D implements IsMCVertex {

		float d = Float.MAX_VALUE;
		boolean inside;
		MCube cube;

		public Cell(double x, double y, double z) {
			super(x, y, z);
			// TODO Auto-generated constructor stub
		}

		public Cell(float x, float y, float z) {
			super(x, y, z);
			// TODO Auto-generated constructor stub
		}

		public float getValue() {
			// TODO Auto-generated method stub
			return d;
		}

		public void setValue(double value) {
			// TODO Auto-generated method stub
			this.d = (float) value;
		}

		public MCube getCube() {
			// TODO Auto-generated method stub
			return cube;
		}

		public void setCube(MCube cube) {
			// TODO Auto-generated method stub
			this.cube = cube;
		}
	}

}
